//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

// Test if we have _GNU_SOURCE before the next step will mess up 
// setting __USE_GNU that we need for string.h to define basename
// (only needed for gcc-2.95 compatibility, gcc 3.2 always defines it)
#include "wx/setup.h"

// Mario Sergio Fujikawa Ferreira <lioux@FreeBSD.org>
// to detect if this is a *BSD system
#if defined(HAVE_SYS_PARAM_H)
#include <sys/param.h>
#endif

//#include "stdafx.h"
#include <wx/msgdlg.h>

#include "emule.h"
#include "emuleDlg.h"
#include "sockets.h"
#include "KnownFileList.h"
#include "KnownFile.h"
#include "ServerList.h"
#include "opcodes.h"
#include "SharedFileList.h"
#include "CreditsDlg.h"
#include "ED2KLink.h"
#include "packets.h"
#include <time.h>

#include <wx/filename.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// *BSD compatibility
// required for basename(1) definition
#if (defined(BSD) && (BSD >= 199103))
#include <libgen.h>
#endif

// so we get basename(1) only if we have _GNU_SOURCE or BSD
// what about other constellations ? -- let's try this:
#ifndef __USE_GNU
#include <libgen.h>
#endif

CSharedFileList::CSharedFileList(CPreferences* in_prefs,CServerConnect* in_server,CKnownFileList* in_filelist){
	app_prefs = in_prefs;
	server = in_server;
	filelist = in_filelist;
	output = 0;
	m_Files_map.InitHashTable(1024);
	FindSharedFiles();
}

CSharedFileList::~CSharedFileList(){
}

void CSharedFileList::FindSharedFiles(){
  if(!theApp.emuledlg->IsRunning()) 
    return;

  if (!m_Files_map.IsEmpty()){
    //CSingleLock sLock1(&list_mut,true); // list thread safe
    list_mut.Lock();
    m_Files_map.RemoveAll();
    //sLock1.Unlock();
    list_mut.Unlock();
    theApp.downloadqueue->AddPartFilesToShare(); // readd partfiles
  }
  AddFilesFromDirectory(app_prefs->GetIncomingDir());
  //for (POSITION pos = app_prefs->shareddir_list.GetHeadPosition();pos != 0;app_prefs->shareddir_list.GetNext(pos))
  // remove bogus entries first
  for(int ij=0;ij<app_prefs->shareddir_list.GetCount();ij++) {
    if(!wxFileName::DirExists(app_prefs->shareddir_list.Item(ij))) {
      app_prefs->shareddir_list.Remove(ij);
      --ij;
    }
  }
  for(int ii=0;ii<app_prefs->shareddir_list.GetCount();ii++) {
    AddFilesFromDirectory((char*)app_prefs->shareddir_list.Item(ii).GetData());
  }
  if (waitingforhash_list.IsEmpty()) 
    theApp.emuledlg->AddLogLine(false,GetResString(IDS_SHAREDFOUND),m_Files_map.GetCount());
  else
    theApp.emuledlg->AddLogLine(false,GetResString(IDS_SHAREDFOUNDHASHING),m_Files_map.GetCount(),waitingforhash_list.GetCount());
  HashNextFile();
}

void CSharedFileList::AddFilesFromDirectory(char* directory){
  //CFileFind ff;
  char* searchpath = new char[strlen(directory)+3];
  sprintf(searchpath,"%s/*",directory);
  //bool end = !ff.FindFile(searchpath,0);
  wxString fname=::wxFindFirstFile(searchpath,wxFILE);
  delete[] searchpath;
  if (fname.IsEmpty())
    return;
  //ff.FindNextFile();
  //while (!end){
  while(!fname.IsEmpty()) {
    //end = !ff.FindNextFile();
    //if (ff.IsDirectory() || ff.IsDots() || ff.IsSystem() || ff.IsTemporary())
    //  continue;
    wxFileName fName(fname);

    //CTime lwtime;
    //ff.GetLastWriteTime(lwtime);
    //uint32 fdate = mktime(lwtime.GetLocalTm());
    wxDateTime accTime,modTime,crtTime;
    fName.GetTimes(&accTime,&modTime,&crtTime);
    uint32 fdate=modTime.GetTicks();
    //CKnownFile* toadd = filelist->FindKnownFile(ff.GetFileName().GetBuffer(),fdate,(uint32)ff.GetLength());
    int koko;
    struct stat sbf;
    stat(fname.GetData(),&sbf);
    koko=sbf.st_size;
    CKnownFile* toadd=filelist->FindKnownFile((char*)fName.GetFullName().GetData(),fdate,koko);
	theApp.Yield();
    if (toadd){
		output->ShowFile(toadd);
      toadd->SetPath(directory);
      //CSingleLock sLock(&list_mut,true);
//      list_mut.Lock();
//      m_Files_map.SetAt(CCKey(toadd->GetFileHash()),toadd);
      //sLock.Unlock();
//      list_mut.Unlock();
    }
    else{
      //not in knownfilelist - start adding thread to hash file
      UnknownFile_Struct* tohash = new UnknownFile_Struct;
      tohash->directory = nstrdup(directory);
      tohash->name = nstrdup(fName.GetFullName().GetData());//GetFileName().GetBuffer());
      waitingforhash_list.AddTail(tohash);
    }
    fname=::wxFindNextFile();
  }
  //ff.Close();
}

void CSharedFileList::SafeAddKFile(CKnownFile* toadd, bool bOnlyAdd){
	// TODO: Check if the file is already known - only with another date
	//CSingleLock sLock(&list_mut,true);
  list_mut.Lock();
  m_Files_map.SetAt(CCKey(toadd->GetFileHash()),toadd);
  //sLock.Unlock();
  list_mut.Unlock();
  
  if (bOnlyAdd)
    return;
  if (output)
    output->ShowFile(toadd);
  printf("Hashing next\n");
  HashNextFile();
  // offer new file to server
  if (!server->IsConnected())
    return;
  CMemFile* files = new CMemFile(100);
  uint32 filecount = 1;
  files->Write(&filecount,4);
  CreateOfferedFilePacket(toadd,files);
  Packet* packet = new Packet(files);
  packet->opcode = OP_OFFERFILES;
  delete files;
  theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
  server->SendPacket(packet,true);
	
}

// removes first occurrence of 'toremove' in 'list'
void CSharedFileList::RemoveFile(CKnownFile* toremove){
	output->RemoveFile(toremove);
	m_Files_map.RemoveKey(CCKey(toremove->GetFileHash()));
}

void CSharedFileList::Reload(bool sendtoserver, bool firstload){
	output->DeleteAllItems();
	this->FindSharedFiles();
	if ((output) && (firstload == false))
		output->ShowFileList(this);
	if (sendtoserver)
		SendListToServer();
}

void CSharedFileList::SetOutputCtrl(CSharedFilesCtrl* in_ctrl){
	output = in_ctrl;
	output->ShowFileList(this);
}

void CSharedFileList::SendListToServer(){
	if (m_Files_map.IsEmpty() || !server->IsConnected())
		return;
	CMemFile* files = new CMemFile();
	uint32 filecount = m_Files_map.GetCount();
	files->Write(&filecount,4);
	
	CCKey bufKey;
	CKnownFile* cur_file;
	for (POSITION pos = m_Files_map.GetStartPosition();pos != 0;){
		m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
		CreateOfferedFilePacket(cur_file,files);
	}
	Packet* packet = new Packet(files);
	packet->opcode = OP_OFFERFILES;
	delete files;
	theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
	server->SendPacket(packet,true);
}

CKnownFile* CSharedFileList::GetFileByIndex(int index){
        int count=0;
        CKnownFile* cur_file;
        CCKey bufKey;
 
        for (POSITION pos = m_Files_map.GetStartPosition();pos != 0;){
                m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
                if (index==count) return cur_file;
                count++;
        }
        return 0;
}

void CSharedFileList::CreateOfferedFilePacket(CKnownFile* cur_file,CMemFile* files){
	files->Write(cur_file->GetFileHash(),16);
	char* buffer = new char[6];
	memset(buffer,0,6);
	files->Write(buffer,6);
	delete[] buffer;
	files->Write(cur_file->GetFileTypePtr(),4);
	CTag* nametag = new CTag(FT_FILENAME,cur_file->GetFileName());
	nametag->WriteTagToFile(files);
	delete nametag;
	CTag* sizetag = new CTag(FT_FILESIZE,cur_file->GetFileSize());
	sizetag->WriteTagToFile(files);
	delete sizetag;
	//TODO add tags for documents mp3 etc
}

uint64 CSharedFileList::GetDatasize() {
	uint64 fsize;
	fsize=0;

	CCKey bufKey;
	CKnownFile* cur_file;
	for (POSITION pos = m_Files_map.GetStartPosition();pos != 0;){
		m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
		fsize+=cur_file->GetFileSize();
	}
	return fsize;
}

CKnownFile*	CSharedFileList::GetFileByID(uchar* filehash){
	CKnownFile* result;
	CCKey tkey(filehash);
	if (m_Files_map.Lookup(tkey,result))
		return result;
	else
		return 0;
}

short CSharedFileList::GetFilePriorityByID(uchar* filehash){
	CKnownFile* tocheck = GetFileByID(filehash);
	if (tocheck)
		return tocheck->GetPriority();
	else
		return -10;	// file doesn't exist
}

void CSharedFileList::HashNextFile(){
	if (waitingforhash_list.IsEmpty())
		return;
	UnknownFile_Struct* nextfile = waitingforhash_list.RemoveHead();
	printf("*** hashnextfile %s \n",nextfile->name);
	//CAddFileThread* addfilethread = (CAddFileThread*) AfxBeginThread(RUNTIME_CLASS(CAddFileThread), THREAD_PRIORITY_BELOW_NORMAL,0, CREATE_SUSPENDED);
	//addfilethread->SetValues(this,nextfile->directory,nextfile->name);
	//addfilethread->ResumeThread();
  CAddFileThread* addfilethread=new CAddFileThread();
  addfilethread->Create();
  //CAddFileThread* addfilethread = (CAddFileThread*) AfxBeginThread(RUNTIME_CLASS(CAddFileThread), THREAD_PRIORITY_BELOW_NORMAL,0, CREATE_SUSPENDED);
  addfilethread->SetValues(this,nextfile->directory,nextfile->name,NULL,theApp.emuledlg);
  //addfilethread->ResumeThread();
  addfilethread->Run();
	delete[] nextfile->directory; 
	delete[] nextfile->name;
	delete nextfile;
	
}

//IMPLEMENT_DYNCREATE(CAddFileThread, CWinThread)
CAddFileThread::CAddFileThread()
  : wxThread(wxTHREAD_DETACHED)
{
	m_pOwner = 0;
	filename = 0;
	directory = 0;
}
void CAddFileThread::SetValues(CSharedFileList* pOwner, char* in_directory, char* in_filename, CPartFile* in_partfile_Owner,wxFrame* p_parent){
	 m_pOwner = pOwner;
#if 0
	 directory = nstrdup(in_directory);
	 // we want to split the file name (it may contain path)
	 wxString volume,path,name,ext;
	 wxFileName::SplitPath(in_filename,&volume,&path,&name,&ext);
	 wxString wanted_name;
	 if(ext="") 
		wanted_name=name;
  	 else wanted_name=name+"."+ext;
	 filename=nstrdup(wanted_name.GetData());
	 //filename = nstrdup(in_filename);
	 partfile_Owner = in_partfile_Owner;
	 parent=p_parent;

	printf("*** in:%s vol:%s path:%s name:%s ext:%s\n",
		in_filename,volume.GetData(),path.GetData(),name.GetData(),
		ext.GetData());
#endif
         m_pOwner = pOwner;
         directory = nstrdup(in_directory);
         filename = nstrdup(basename(in_filename));
         partfile_Owner = in_partfile_Owner;

	 printf("*** setting %s and %s\n",directory,filename);
}

wxThread::ExitCode CAddFileThread::Entry(){

  if (!(m_pOwner || partfile_Owner) || !filename) {
    //AfxEndThread(0,true);
    return 0;
  }
  //CSingleLock sLock1(&(theApp.hashing_mut)); // only one filehash at a time
  //sLock1.Lock();
  wxMutexLocker sLock1(theApp.hashing_mut);
  CKnownFile* newrecord = new CKnownFile();
  printf("Trying to open %s %s\n",directory,filename);
  if (newrecord->CreateFromFile(directory,filename)){
    //PostMessage(theApp.emuledlg->m_hWnd,TM_FINISHEDHASHING,(m_pOwner ? 0:(WPARAM)partfile_Owner),(LPARAM)newrecord);   
    wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED,TM_FINISHEDHASHING);
    evt.SetClientData(newrecord);
    evt.SetInt(m_pOwner?0:(int)partfile_Owner);

    //wxCommandEvent evt(wxEVT_COMMAND_BUTTON_CLICKED,42111);
    wxPostEvent(theApp.emuledlg,evt);
    //printf("Posting to %lx\n",parent);
    //wxPostEvent(parent,evt);
    //((CemuleDlg*)parent)->OnFinishedHashing(evt);

    //theApp.emuledlg->GetEventHandler()->AddPendingEvent(evt);
    //theApp.emuledlg->GetEventHandler()->ProcessEvent(evt);
    
    //printf("todo post message\n");
    //printf("** posted FINISHEDHASHING\n");
  }
  else{
    delete newrecord;
  }
  
  delete[] filename;
  if (directory)
		delete[] directory;
  // will be unlocked automagically
  //sLock1.Unlock();
  //AfxEndThread(0,true);
  //printf("Thread: Thank you. exiting\n");
  return 0;
}

void CSharedFileList::UpdateItem(CKnownFile* toupdate) {output->UpdateItem(toupdate);}
